/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file shader.hpp
 * @author TooOld2Rock'nRoll
 * @date 2022/Ago
 * @remark - Requires a working copy for your system of the Glad library.
 *
 * @brief Shader Class for the ArcadeFighter library.
 *
 * Originally this code was part of Breakout, now part of ArcadeFighter library
 *  and was modified by the author as a exercise.<br>
 * Breakout is free software: you can redistribute it and/or modify it under the terms of the CC BY 4.0 license as
 *    published by Creative Commons, either version 4 of the License, or (at your option) any later version.<br><br>
 * Glad is published under the (WTFPL OR CC0-1.0) AND Apache-2.0 licenses.
 *
 * @see https://learnopengl.com/In-Practice/2D-Game/Breakout
 * @see https://glad.dav1d.de/
 *
 * @todo - projection::PERSPECTIVE untested!!
 */
#ifndef _SHADER_HPP_
#define _SHADER_HPP_

/*---- Includes ----*/
#include <atomic>

#include "graphics/opengl.hpp"


/*---- Typedefs ----*/
/** @brief A more elegant name to keep track of Gl shader objects. */
typedef GLuint ShaderID_t;


/*---- Classes ----*/
/**
 * @brief Class to load and intermediate the use of OpenGl shader objects operations.
 *
 * This class is an abstraction for the GPU rendering pipeline which is composed of Shader Programs, each one
 *    responsible for a specific task. These programs are written in a C-like language called GLSL and are very
 *    isolated from each other except via their inputs/outputs and uniforms.<br>
 * A minimal setup of a Vertex Shader and a Fragment Shader is required for proper rendering and all rendering/draw
 *    operations require a valid Shader Object. We provide a basic set of shader programs with the usual uniforms
 *    to help you settle in, other classes will take care of the details of changing the programs states, but you
 *    are still required to instantiate a Shader, compile() its programs and choose a Perspective.<br>
 * You are encouraged to write your own shader programs with specific behavior and optimizations for your project
 *    needs, but if you intend on using this library rendering classes, there are some uniforms and inputs/outputs
 *    you need to consider to make it work. Take a look at our shader.vert and shader.frag for more references.
 *
 * @see https://www.khronos.org/opengl/wiki/Shader
 * @see https://learnopengl.com/Getting-started/Shaders
 * @see https://www.khronos.org/opengl/wiki/Shader_Compilation
 * @see https://www.khronos.org/opengl/wiki/OpenGL_Shading_Language
 * @see shaders/shader.vert
 * @see shaders/shader.frag
 */
class Shader
{
    private:
        static std::atomic<ShaderID_t> last_shader;///< Keep track of last Shader used to optimize some operations

        ShaderID_t ID = 0;///< The Shader Identification.
        std::atomic<bool> b_compiled = false;

        std::mutex _compilation_semaphore; ///<To be sure shader is not being compiled in parallel by accident...


    protected:


    public:
        Shader ();
        ~Shader ();

        /** @brief Gets the Gl shader identity. */
        ShaderID_t getID () const { return this->ID; }

        void use ();

        void compile (const char *vertex_src, const char *fragment_src, const char *geometry_src = nullptr);
        /** @brief Returns the shader state, <b>true</b> if compiled or <b>false</b> is not. */
        bool isCompiled () const { return this->b_compiled; }
        void loadFromFile (const char *vertex_file, const char *fragment_file, const char *geometry_file = nullptr);

        // utility functions
        void  setUniform (const char *name, float value);
        void  setUniform (const char *name, int value);

        void  setUniform (const char *name, float x, float y);
        /** @brief Sets a glm::vec2 to a shader uniform.  */
        void  setUniform (const char *name, const glm::vec2 &value) { this->setUniform (name, value.x, value.y); }

        void  setUniform (const char *name, float x, float y, float z);
        /** @brief Sets a glm::vec3 to a shader uniform.  */
        void  setUniform (const char *name, const glm::vec3 &value) { this->setUniform (name, value.x, value.y, value.z); }

        void  setUniform (const char *name, float x, float y, float z, float w);
        /** @brief Sets a glm::vec4 to a shader uniform.  */
        void  setUniform (const char *name, const glm::vec4 &value) { this->setUniform (name, value.x, value.y, value.z, value.w); }

        void  setUniform (const char *name, const glm::mat4 &matrix);

        //automate some common tasks....
        glm::mat4 setOrthographicProjection (float left, float top, float right, float bottom, float zNear = -1.f, float zFar = 1.f, const char *uniform_name = "projection");
        glm::mat4 setPerspectiveProjection (float fovy, float aspect, float zNear = 1.f, float zFar = 100.f, const char *uniform_name = "projection");

        void setCameraView (glm::mat4 view, const char *uniform_name = "view");
};//End Shader

#endif //_SHADER_HPP_

